//          Copyright Louis Delacroix 2010 - 2014.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)
//
// A pretty printing library for C++
//
// Usage:
// Include this header, and operator<< will "just work".

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT

#include <cstddef>
#include <iterator>
#include <memory>
#include <ostream>
#include <set>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <valarray>

namespace pretty_print {
  namespace detail {
      // SFINAE type trait to detect whether T::const_iterator exists.

    struct sfinae_base {
      using yes = char;
      using no  = yes[ 2 ];
    };

    template <typename T>
    struct has_const_iterator : private sfinae_base {
    private:
      template <typename C> static yes &test(typename C::const_iterator *);
      template <typename C> static no &test(...);
    public:
      static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
      using type =  T;
    };

    template <typename T>
    struct has_begin_end : private sfinae_base {
    private:
      template <typename C>
      static yes &f(typename std::enable_if<
                    std::is_same<decltype(static_cast<typename C::const_iterator(C:: *)() const>(&C::begin)),
                    typename C::const_iterator(C:: *)() const>::value>::type *);

      template <typename C> static no &f(...);

      template <typename C>
      static yes &g(typename std::enable_if<
                    std::is_same<decltype(static_cast<typename C::const_iterator(C:: *)() const>(&C::end)),
                    typename C::const_iterator(C:: *)() const>::value, void>::type *);

      template <typename C> static no &g(...);

    public:
      static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
      static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);
    };
  }  // namespace detail

  // Holds the delimiter values for a specific character type

  template <typename TChar>
  struct delimiters_values {
    using char_type = TChar;
    const char_type *prefix;
    const char_type *delimiter;
    const char_type *postfix;
  };

  // Defines the delimiter values for a specific container and character type

  template <typename T, typename TChar>
  struct delimiters {
    using type = delimiters_values<TChar>;
    static const type values;
  };

  // Functor to print containers. You can use this directly if you want
  // to specificy a non-default delimiters type. The printing logic can
  // be customized by specializing the nested template.

  template <typename T,
    typename TChar = char,
    typename TCharTraits = ::std::char_traits<TChar>,
    typename TDelimiters = delimiters<T, TChar>>
    struct print_container_helper {
    using delimiters_type = TDelimiters;
    using ostream_type = std::basic_ostream<TChar, TCharTraits>;

    template <typename U>
    struct printer {
      static void print_body(const U &c, ostream_type &stream) {
        using std::begin;
        using std::end;

        auto it = begin(c);
        const auto the_end = end(c);

        if (it != the_end) {
          for (; ; ) {
            stream << *it;

            if (++it == the_end) break;

            if (delimiters_type::values.delimiter != NULL)
              stream << delimiters_type::values.delimiter;
          }
        }
      }
    };

    print_container_helper(const T &container)
      : container_(container) {
    }

    inline void operator()(ostream_type &stream) const {
      if (delimiters_type::values.prefix != NULL)
        stream << delimiters_type::values.prefix;

      printer<T>::print_body(container_, stream);

      if (delimiters_type::values.postfix != NULL)
        stream << delimiters_type::values.postfix;
    }

    private:
      const T &container_;
  };

  // Specialization for pairs

  template <typename T, typename TChar, typename TCharTraits, typename TDelimiters>
  template <typename T1, typename T2>
  struct print_container_helper<T, TChar, TCharTraits, TDelimiters>::printer<std::pair<T1, T2>> {
    using ostream_type = typename print_container_helper<T, TChar, TCharTraits, TDelimiters>::ostream_type;

    static void print_body(const std::pair<T1, T2> &c, ostream_type &stream) {
      stream << c.first;
      if (print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter != NULL)
        stream << print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter;
      stream << c.second;
    }
  };

  // Specialization for tuples

  template <typename T, typename TChar, typename TCharTraits, typename TDelimiters>
  template <typename ...Args>
  struct print_container_helper<T, TChar, TCharTraits, TDelimiters>::printer<std::tuple<Args...>> {
    using ostream_type = typename print_container_helper<T, TChar, TCharTraits, TDelimiters>::ostream_type;
    using element_type = std::tuple<Args...>;

    template <std::size_t I> struct Int {};

    static void print_body(const element_type &c, ostream_type &stream) {
      tuple_print(c, stream, Int<0>());
    }

    static void tuple_print(const element_type &, ostream_type &, Int<sizeof...(Args)>) {
    }

    static void tuple_print(const element_type &c, ostream_type &stream,
                            typename std::conditional<sizeof...(Args) != 0, Int<0>, std::nullptr_t>::type) {
      stream << std::get<0>(c);
      tuple_print(c, stream, Int<1>());
    }

    template <std::size_t N>
    static void tuple_print(const element_type &c, ostream_type &stream, Int<N>) {
      if (print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter != NULL)
        stream << print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter;

      stream << std::get<N>(c);

      tuple_print(c, stream, Int<N + 1>());
    }
  };

  // Prints a print_container_helper to the specified stream.

  template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
  inline std::basic_ostream<TChar, TCharTraits> &operator<<(
    std::basic_ostream<TChar, TCharTraits> &stream,
    const print_container_helper<T, TChar, TCharTraits, TDelimiters> &helper) {
    helper(stream);
    return stream;
  }

  // Basic is_container template; specialize to derive from std::true_type for all desired container types

  template <typename T>
  struct is_container : public std::integral_constant<bool,
    detail::has_const_iterator<T>::value &&
    detail::has_begin_end<T>::beg_value &&
    detail::has_begin_end<T>::end_value> {
  };

  template <typename T, std::size_t N>
  struct is_container<T[ N ]> : std::true_type {};

  template <std::size_t N>
  struct is_container<char[ N ]> : std::false_type {};

  template <typename T>
  struct is_container<std::valarray<T>> : std::true_type {};

  template <typename T1, typename T2>
  struct is_container<std::pair<T1, T2>> : std::true_type {};

  template <typename ...Args>
  struct is_container<std::tuple<Args...>> : std::true_type {};

  // Default delimiters

  template <typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
  template <typename T> const delimiters_values<char> delimiters<T, char>::values = { "[", ", ", "]" };
  template <typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
  template <typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"[", L", ", L"]" };

  // Delimiters for (multi)set and unordered_(multi)set

  template <typename T, typename TComp, typename TAllocator>
  struct delimiters< ::std::set<T, TComp, TAllocator>, char> { static const delimiters_values<char> values; };

  template <typename T, typename TComp, typename TAllocator>
  const delimiters_values<char> delimiters< ::std::set<T, TComp, TAllocator>, char>::values = { "{", ", ", "}" };

  template <typename T, typename TComp, typename TAllocator>
  struct delimiters< ::std::set<T, TComp, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };

  template <typename T, typename TComp, typename TAllocator>
  const delimiters_values<wchar_t> delimiters< ::std::set<T, TComp, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };

  template <typename T, typename TComp, typename TAllocator>
  struct delimiters< ::std::multiset<T, TComp, TAllocator>, char> { static const delimiters_values<char> values; };

  template <typename T, typename TComp, typename TAllocator>
  const delimiters_values<char> delimiters< ::std::multiset<T, TComp, TAllocator>, char>::values = { "{", ", ", "}" };

  template <typename T, typename TComp, typename TAllocator>
  struct delimiters< ::std::multiset<T, TComp, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };

  template <typename T, typename TComp, typename TAllocator>
  const delimiters_values<wchar_t> delimiters< ::std::multiset<T, TComp, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  struct delimiters< ::std::unordered_set<T, THash, TEqual, TAllocator>, char> { static const delimiters_values<char> values; };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  const delimiters_values<char> delimiters< ::std::unordered_set<T, THash, TEqual, TAllocator>, char>::values = { "{", ", ", "}" };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  struct delimiters< ::std::unordered_set<T, THash, TEqual, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  const delimiters_values<wchar_t> delimiters< ::std::unordered_set<T, THash, TEqual, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  struct delimiters< ::std::unordered_multiset<T, THash, TEqual, TAllocator>, char> { static const delimiters_values<char> values; };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  const delimiters_values<char> delimiters< ::std::unordered_multiset<T, THash, TEqual, TAllocator>, char>::values = { "{", ", ", "}" };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  struct delimiters< ::std::unordered_multiset<T, THash, TEqual, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };

  template <typename T, typename THash, typename TEqual, typename TAllocator>
  const delimiters_values<wchar_t> delimiters< ::std::unordered_multiset<T, THash, TEqual, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };

  // Delimiters for pair and tuple

  template <typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
  template <typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
  template <typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
  template <typename T1, typename T2> const delimiters_values<wchar_t> delimiters< ::std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };

  template <typename ...Args> struct delimiters<std::tuple<Args...>, char> { static const delimiters_values<char> values; };
  template <typename ...Args> const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = { "(", ", ", ")" };
  template <typename ...Args> struct delimiters< ::std::tuple<Args...>, wchar_t> { static const delimiters_values<wchar_t> values; };
  template <typename ...Args> const delimiters_values<wchar_t> delimiters< ::std::tuple<Args...>, wchar_t>::values = { L"(", L", ", L")" };

  // Type-erasing helper class for easy use of custom delimiters.
  // Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar.
  // Usage: "cout << pretty_print::custom_delims<MyDelims>(x)".

  struct custom_delims_base {
    virtual ~custom_delims_base() {}
    virtual std::ostream &stream(::std::ostream &) = 0;
    virtual std::wostream &stream(::std::wostream &) = 0;
  };

  template <typename T, typename Delims>
  struct custom_delims_wrapper : custom_delims_base {
    custom_delims_wrapper(const T &t_) : t(t_) {}

    std::ostream &stream(std::ostream &s) {
      return s << print_container_helper<T, char, std::char_traits<char>, Delims>(t);
    }

    std::wostream &stream(std::wostream &s) {
      return s << print_container_helper<T, wchar_t, std::char_traits<wchar_t>, Delims>(t);
    }

  private:
    const T &t;
  };

  template <typename Delims>
  struct custom_delims {
    template <typename Container>
    custom_delims(const Container &c) : base(new custom_delims_wrapper<Container, Delims>(c)) {}

    std::unique_ptr<custom_delims_base> base;
  };

  template <typename TChar, typename TCharTraits, typename Delims>
  inline std::basic_ostream<TChar, TCharTraits> &operator<<(std::basic_ostream<TChar, TCharTraits> &s, const custom_delims<Delims> &p) {
    return p.base->stream(s);
  }

  // A wrapper for a C-style array given as pointer-plus-size.
  // Usage: std::cout << pretty_print_array(arr, n) << std::endl;

  template<typename T>
  struct array_wrapper_n {
    typedef const T *const_iterator;
    typedef T value_type;

    array_wrapper_n(const T *const a, size_t n) : _array(a), _n(n) {}
    inline const_iterator begin() const { return _array; }
    inline const_iterator end() const { return _array + _n; }

  private:
    const T *const _array;
    size_t _n;
  };

  // A wrapper for hash-table based containers that offer local iterators to each bucket.
  // Usage: std::cout << bucket_print(m, 4) << std::endl;  (Prints bucket 5 of container m.)

  template <typename T>
  struct bucket_print_wrapper {
    typedef typename T::const_local_iterator const_iterator;
    typedef typename T::size_type size_type;

    const_iterator begin() const {
      return m_map.cbegin(n);
    }

    const_iterator end() const {
      return m_map.cend(n);
    }

    bucket_print_wrapper(const T &m, size_type bucket) : m_map(m), n(bucket) {}

  private:
    const T &m_map;
    const size_type n;
  };
}   // namespace pretty_print

// Global accessor functions for the convenience wrappers

template<typename T>
inline pretty_print::array_wrapper_n<T> pretty_print_array(const T *const a, size_t n) {
  return pretty_print::array_wrapper_n<T>(a, n);
}

template <typename T> pretty_print::bucket_print_wrapper<T>
        bucket_print(const T &m, typename T::size_type n) {
          return pretty_print::bucket_print_wrapper<T>(m, n);
        }

        // Main magic entry point: An overload snuck into namespace std.
        // Can we do better?

        namespace std {
            // Prints a container to the stream using default delimiters

          template<typename T, typename TChar, typename TCharTraits>
          inline typename enable_if< ::pretty_print::is_container<T>::value,
            basic_ostream<TChar, TCharTraits> &>::type
            operator<<(basic_ostream<TChar, TCharTraits> &stream, const T &container) {
            return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container);
          }
        }

#endif  // H_PRETTY_PRINT